/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
 
This file is part of Quake III Arena source code.
 
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
 
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
//
/*
=======================================================================
 
MULTIPLAYER MENU (SERVER BROWSER)
 
=======================================================================
*/


#include "ui_local.h"


#define MAX_GLOBALSERVERS		128
#define MAX_PINGREQUESTS		32
#define MAX_ADDRESSLENGTH		64
#define MAX_HOSTNAMELENGTH		22
#define MAX_MAPNAMELENGTH		16
#define MAX_LISTBOXITEMS		128
#define MAX_LOCALSERVERS		128
#define MAX_STATUSLENGTH		64
#define MAX_LEAGUELENGTH		28
#define MAX_LISTBOXWIDTH		68

#define ART_BACK0				"menu/art/back_0"
#define ART_BACK1				"menu/art/back_1"
#define ART_CREATE0				"menu/art/create_0"
#define ART_CREATE1				"menu/art/create_1"
#define ART_SPECIFY0			"menu/art/specify_0"
#define ART_SPECIFY1			"menu/art/specify_1"
#define ART_REFRESH0			"menu/art/refresh_0"
#define ART_REFRESH1			"menu/art/refresh_1"
#define ART_CONNECT0			"menu/art/fight_0"
#define ART_CONNECT1			"menu/art/fight_1"
#define ART_ARROWS0				"menu/art/arrows_vert_0"
#define ART_ARROWS_UP			"menu/art/arrows_vert_top"
#define ART_ARROWS_DOWN			"menu/art/arrows_vert_bot"
#define ART_UNKNOWNMAP			"menu/art/unknownmap"
#define ART_REMOVE0				"menu/art/delete_0"
#define ART_REMOVE1				"menu/art/delete_1"
#define ART_PUNKBUSTER		"menu/art/pblogo"

#define ID_MASTER			10
#define ID_GAMETYPE			11
#define ID_SORTKEY			12
#define ID_SHOW_FULL		13
#define ID_SHOW_EMPTY		14
#define ID_LIST				15
#define ID_SCROLL_UP		16
#define ID_SCROLL_DOWN		17
#define ID_BACK				18
#define ID_REFRESH			19
#define ID_SPECIFY			20
#define ID_CREATE			21
#define ID_CONNECT			22
#define ID_REMOVE			23
#define ID_PUNKBUSTER 24

#define GR_LOGO				30
#define GR_LETTERS			31

#define AS_LOCAL			0
#define AS_MPLAYER			1
#define AS_GLOBAL			2
#define AS_FAVORITES		3

#define SORT_HOST			0
#define SORT_MAP			1
#define SORT_CLIENTS		2
#define SORT_GAME			3
#define SORT_PING			4

#define GAMES_ALL			0
#define GAMES_FFA			1
#define GAMES_TEAMPLAY		2
#define GAMES_TOURNEY		3
#define GAMES_CTF			4

static const char *master_items[] =
    {
        "Local",
        "Internet",
        "Favorites",
        0
    };

static const char *servertype_items[] =
    {
        "All",
        "Free For All",
        "Team Deathmatch",
        "Tournament",
        "Capture the Flag",
        0
    };

static const char *sortkey_items[] =
    {
        "Server Name",
        "Map Name",
        "Open Player Spots",
        "Game Type",
        "Ping Time",
        0
    };

static char* gamenames[] = {
                               "DM ",	// deathmatch
                               "1v1",	// tournament
                               "SP ",	// single player
                               "Team DM",	// team deathmatch
                               "CTF",	// capture the flag
                               "One Flag CTF",		// one flag ctf
                               "OverLoad",				// Overload
                               "Harvester",			// Harvester
                               "Rocket Arena 3",	// Rocket Arena 3
                               "Q3F",						// Q3F
                               "Urban Terror",		// Urban Terror
                               "OSP",						// Orange Smoothie Productions
                               "???",			// unknown
                               0
                           };

static char* netnames[] = {
                              "???",
                              "UDP",
                              "IPX",
                              NULL
                          };

static char quake3worldMessage[] = "Visit www.quake3world.com - News, Community, Events, Files";

const char* punkbuster_items[] =
    {
        "Disabled",
        "Enabled",
        NULL
    };

const char* punkbuster_msg[] =
    {
        "PunkBuster will be",
        "disabled the next time",
        "Quake III Arena",
        "is started.",
        NULL
    };

typedef struct
{
    char	adrstr[MAX_ADDRESSLENGTH];
    int		start;
}
pinglist_t;

typedef struct servernode_s
{
    char	adrstr[MAX_ADDRESSLENGTH];
    char	hostname[MAX_HOSTNAMELENGTH+3];
    char	mapname[MAX_MAPNAMELENGTH];
    int		numclients;
    int		maxclients;
    int		pingtime;
    int		gametype;
    char	gamename[12];
    int		nettype;
    int		minPing;
    int		maxPing;
    qboolean bPB;

}
servernode_t;

typedef struct
{
    char			buff[MAX_LISTBOXWIDTH];
    servernode_t*	servernode;
}
table_t;

typedef struct
{
    menuframework_s		menu;

    menutext_s			banner;

    menulist_s			master;
    menulist_s			gametype;
    menulist_s			sortkey;
    menuradiobutton_s	showfull;
    menuradiobutton_s	showempty;

    menulist_s			list;
    menubitmap_s		mappic;
    menubitmap_s		arrows;
    menubitmap_s		up;
    menubitmap_s		down;
    menutext_s			status;
    menutext_s			statusbar;

    menubitmap_s		remove;
    menubitmap_s		back;
    menubitmap_s		refresh;
    menubitmap_s		specify;
    menubitmap_s		create;
    menubitmap_s		go;

    pinglist_t			pinglist[MAX_PINGREQUESTS];
    table_t				table[MAX_LISTBOXITEMS];
    char*				items[MAX_LISTBOXITEMS];
    int					numqueriedservers;
    int					*numservers;
    servernode_t		*serverlist;
    int					currentping;
    qboolean			refreshservers;
    int					nextpingtime;
    int					maxservers;
    int					refreshtime;
    char				favoriteaddresses[MAX_FAVORITESERVERS][MAX_ADDRESSLENGTH];
    int					numfavoriteaddresses;

    menulist_s		punkbuster;
    menubitmap_s	pblogo;
}
arenaservers_t;

static arenaservers_t	g_arenaservers;


static servernode_t		g_globalserverlist[MAX_GLOBALSERVERS];
static int				g_numglobalservers;
static servernode_t		g_localserverlist[MAX_LOCALSERVERS];
static int				g_numlocalservers;
static servernode_t		g_favoriteserverlist[MAX_FAVORITESERVERS];
static int				g_numfavoriteservers;
static servernode_t		g_mplayerserverlist[MAX_GLOBALSERVERS];
static int				g_nummplayerservers;
static int				g_servertype;
static int				g_gametype;
static int				g_sortkey;
static int				g_emptyservers;
static int				g_fullservers;


/*
=================
ArenaServers_MaxPing
=================
*/
static int ArenaServers_MaxPing( void )
{
    int		maxPing;

    maxPing = (int)Cvar_VariableValue( "cl_maxPing" );
    if( maxPing < 100 )
    {
        maxPing = 100;
    }
    return maxPing;
}


/*
=================
ArenaServers_Compare
=================
*/
static int QDECL ArenaServers_Compare( const void *arg1, const void *arg2 )
{
    float			f1;
    float			f2;
    servernode_t*	t1;
    servernode_t*	t2;

    t1 = (servernode_t *)arg1;
    t2 = (servernode_t *)arg2;

    switch( g_sortkey )
    {
    case SORT_HOST:
        return Q_stricmp( t1->hostname, t2->hostname );

    case SORT_MAP:
        return Q_stricmp( t1->mapname, t2->mapname );

    case SORT_CLIENTS:
        f1 = t1->maxclients - t1->numclients;
        if( f1 < 0 )
        {
            f1 = 0;
        }

        f2 = t2->maxclients - t2->numclients;
        if( f2 < 0 )
        {
            f2 = 0;
        }

        if( f1 < f2 )
        {
            return 1;
        }
        if( f1 == f2 )
        {
            return 0;
        }
        return -1;

    case SORT_GAME:
        if( t1->gametype < t2->gametype )
        {
            return -1;
        }
        if( t1->gametype == t2->gametype )
        {
            return 0;
        }
        return 1;

    case SORT_PING:
        if( t1->pingtime < t2->pingtime )
        {
            return -1;
        }
        if( t1->pingtime > t2->pingtime )
        {
            return 1;
        }
        return Q_stricmp( t1->hostname, t2->hostname );
    }

    return 0;
}


/*
=================
ArenaServers_Go
=================
*/
static void ArenaServers_Go( void )
{
    servernode_t*	servernode;

    servernode = g_arenaservers.table[g_arenaservers.list.curvalue].servernode;
    if( servernode )
    {
        Cbuf_ExecuteText( EXEC_APPEND, va( "connect %s\n", servernode->adrstr ) );
    }
}


/*
=================
ArenaServers_UpdatePicture
=================
*/
static void ArenaServers_UpdatePicture( void )
{
    static char		picname[64];
    servernode_t*	servernodeptr;

    if( !g_arenaservers.list.numitems )
    {
        g_arenaservers.mappic.generic.name = NULL;
    }
    else
    {
        servernodeptr = g_arenaservers.table[g_arenaservers.list.curvalue].servernode;
        Com_sprintf( picname, sizeof(picname), "levelshots/%s.tga", servernodeptr->mapname );
        g_arenaservers.mappic.generic.name = picname;

    }

    // force shader update during draw
    g_arenaservers.mappic.shader = 0;
}


/*
=================
ArenaServers_UpdateMenu
=================
*/
static void ArenaServers_UpdateMenu( void )
{
    int				i;
    int				j;
    int				count;
    char*			buff;
    servernode_t*	servernodeptr;
    table_t*		tableptr;
    char			*pingColor;

    if( g_arenaservers.numqueriedservers > 0 )
    {
        // servers found
        if( g_arenaservers.refreshservers && ( g_arenaservers.currentping <= g_arenaservers.numqueriedservers ) )
        {
            // show progress
            Com_sprintf( g_arenaservers.status.string, MAX_STATUSLENGTH, "%d of %d Arena Servers.", g_arenaservers.currentping, g_arenaservers.numqueriedservers);
            g_arenaservers.statusbar.string  = "Press SPACE to stop";
            qsort( g_arenaservers.serverlist, *g_arenaservers.numservers, sizeof( servernode_t ), ArenaServers_Compare);
        }
        else
        {
            // all servers pinged - enable controls
            g_arenaservers.master.generic.flags		&= ~QMF_GRAYED;
            g_arenaservers.gametype.generic.flags	&= ~QMF_GRAYED;
            g_arenaservers.sortkey.generic.flags	&= ~QMF_GRAYED;
            g_arenaservers.showempty.generic.flags	&= ~QMF_GRAYED;
            g_arenaservers.showfull.generic.flags	&= ~QMF_GRAYED;
            g_arenaservers.list.generic.flags		&= ~QMF_GRAYED;
            g_arenaservers.refresh.generic.flags	&= ~QMF_GRAYED;
            g_arenaservers.go.generic.flags			&= ~QMF_GRAYED;
            g_arenaservers.punkbuster.generic.flags &= ~QMF_GRAYED;

            // update status bar
            if( g_servertype == AS_GLOBAL || g_servertype == AS_MPLAYER )
            {
                g_arenaservers.statusbar.string = quake3worldMessage;
            }
            else
            {
                g_arenaservers.statusbar.string = "";
            }

        }
    }
    else
    {
        // no servers found
        if( g_arenaservers.refreshservers )
        {
            strcpy( g_arenaservers.status.string,"Scanning For Servers." );
            g_arenaservers.statusbar.string = "Press SPACE to stop";

            // disable controls during refresh
            g_arenaservers.master.generic.flags		|= QMF_GRAYED;
            g_arenaservers.gametype.generic.flags	|= QMF_GRAYED;
            g_arenaservers.sortkey.generic.flags	|= QMF_GRAYED;
            g_arenaservers.showempty.generic.flags	|= QMF_GRAYED;
            g_arenaservers.showfull.generic.flags	|= QMF_GRAYED;
            g_arenaservers.list.generic.flags		|= QMF_GRAYED;
            g_arenaservers.refresh.generic.flags	|= QMF_GRAYED;
            g_arenaservers.go.generic.flags			|= QMF_GRAYED;
            g_arenaservers.punkbuster.generic.flags |= QMF_GRAYED;
        }
        else
        {
            if( g_arenaservers.numqueriedservers < 0 )
            {
                strcpy(g_arenaservers.status.string,"No Response From Master Server." );
            }
            else
            {
                strcpy(g_arenaservers.status.string,"No Servers Found." );
            }

            // update status bar
            if( g_servertype == AS_GLOBAL || g_servertype == AS_MPLAYER )
            {
                g_arenaservers.statusbar.string = quake3worldMessage;
            }
            else
            {
                g_arenaservers.statusbar.string = "";
            }

            // end of refresh - set control state
            g_arenaservers.master.generic.flags		&= ~QMF_GRAYED;
            g_arenaservers.gametype.generic.flags	&= ~QMF_GRAYED;
            g_arenaservers.sortkey.generic.flags	&= ~QMF_GRAYED;
            g_arenaservers.showempty.generic.flags	&= ~QMF_GRAYED;
            g_arenaservers.showfull.generic.flags	&= ~QMF_GRAYED;
            g_arenaservers.list.generic.flags		|= QMF_GRAYED;
            g_arenaservers.refresh.generic.flags	&= ~QMF_GRAYED;
            g_arenaservers.go.generic.flags			|= QMF_GRAYED;
            g_arenaservers.punkbuster.generic.flags &= ~QMF_GRAYED;
        }

        // zero out list box
        g_arenaservers.list.numitems = 0;
        g_arenaservers.list.curvalue = 0;
        g_arenaservers.list.top      = 0;

        // update picture
        ArenaServers_UpdatePicture();
        return;
    }

    // build list box strings - apply culling filters
    servernodeptr = g_arenaservers.serverlist;
    count         = *g_arenaservers.numservers;
    for( i = 0, j = 0; i < count; i++, servernodeptr++ )
    {
        tableptr = &g_arenaservers.table[j];
        tableptr->servernode = servernodeptr;
        buff = tableptr->buff;

        // can only cull valid results
        if( !g_emptyservers && !servernodeptr->numclients )
        {
            continue;
        }

        if( !g_fullservers && ( servernodeptr->numclients == servernodeptr->maxclients ) )
        {
            continue;
        }

        switch( g_gametype )
        {
        case GAMES_ALL:
            break;

        case GAMES_FFA:
            if( servernodeptr->gametype != GT_FFA )
            {
                continue;
            }
            break;

        case GAMES_TEAMPLAY:
            if( servernodeptr->gametype != GT_TEAM )
            {
                continue;
            }
            break;

        case GAMES_TOURNEY:
            if( servernodeptr->gametype != GT_TOURNAMENT )
            {
                continue;
            }
            break;

        case GAMES_CTF:
            if( servernodeptr->gametype != GT_CTF )
            {
                continue;
            }
            break;
        }

        if( servernodeptr->pingtime < servernodeptr->minPing )
        {
            pingColor = S_COLOR_BLUE;
        }
        else if( servernodeptr->maxPing && servernodeptr->pingtime > servernodeptr->maxPing )
        {
            pingColor = S_COLOR_BLUE;
        }
        else if( servernodeptr->pingtime < 200 )
        {
            pingColor = S_COLOR_GREEN;
        }
        else if( servernodeptr->pingtime < 400 )
        {
            pingColor = S_COLOR_YELLOW;
        }
        else
        {
            pingColor = S_COLOR_RED;
        }

        Com_sprintf( buff, MAX_LISTBOXWIDTH, "%-20.20s %-12.12s %2d/%2d %-8.8s %3s %s%3d " S_COLOR_YELLOW "%s",
                     servernodeptr->hostname, servernodeptr->mapname, servernodeptr->numclients,
                     servernodeptr->maxclients, servernodeptr->gamename,
                     netnames[servernodeptr->nettype], pingColor, servernodeptr->pingtime, servernodeptr->bPB ? "Yes" : "No" );
        j++;
    }

    g_arenaservers.list.numitems = j;
    g_arenaservers.list.curvalue = 0;
    g_arenaservers.list.top      = 0;

    // update picture
    ArenaServers_UpdatePicture();
}


/*
=================
ArenaServers_Remove
=================
*/
static void ArenaServers_Remove( void )
{
    int				i;
    servernode_t*	servernodeptr;
    table_t*		tableptr;

    if (!g_arenaservers.list.numitems)
        return;

    // remove selected item from display list
    // items are in scattered order due to sort and cull
    // perform delete on list box contents, resync all lists

    tableptr      = &g_arenaservers.table[g_arenaservers.list.curvalue];
    servernodeptr = tableptr->servernode;

    // find address in master list
    for (i=0; i<g_arenaservers.numfavoriteaddresses; i++)
        if (!Q_stricmp(g_arenaservers.favoriteaddresses[i],servernodeptr->adrstr))
            break;

    // delete address from master list
    if (i <= g_arenaservers.numfavoriteaddresses-1)
    {
        if (i < g_arenaservers.numfavoriteaddresses-1)
        {
            // shift items up
            memcpy( &g_arenaservers.favoriteaddresses[i], &g_arenaservers.favoriteaddresses[i+1], (g_arenaservers.numfavoriteaddresses - i - 1)*sizeof(MAX_ADDRESSLENGTH));
        }
        g_arenaservers.numfavoriteaddresses--;
    }

    // find address in server list
    for (i=0; i<g_numfavoriteservers; i++)
        if (&g_favoriteserverlist[i] == servernodeptr)
            break;

    // delete address from server list
    if (i <= g_numfavoriteservers-1)
    {
        if (i < g_numfavoriteservers-1)
        {
            // shift items up
            memcpy( &g_favoriteserverlist[i], &g_favoriteserverlist[i+1], (g_numfavoriteservers - i - 1)*sizeof(servernode_t));
        }
        g_numfavoriteservers--;
    }

    g_arenaservers.numqueriedservers = g_arenaservers.numfavoriteaddresses;
    g_arenaservers.currentping       = g_arenaservers.numfavoriteaddresses;
}


/*
=================
ArenaServers_Insert
=================
*/
static void ArenaServers_Insert( char* adrstr, char* info, int pingtime )
{
    servernode_t*	servernodeptr;
    char*			s;
    int				i;


    if ((pingtime >= ArenaServers_MaxPing()) && (g_servertype != AS_FAVORITES))
    {
        // slow global or local servers do not get entered
        return;
    }

    if (*g_arenaservers.numservers >= g_arenaservers.maxservers)
    {
        // list full;
        servernodeptr = g_arenaservers.serverlist+(*g_arenaservers.numservers)-1;
    }
    else
    {
        // next slot
        servernodeptr = g_arenaservers.serverlist+(*g_arenaservers.numservers);
        (*g_arenaservers.numservers)++;
    }

    Q_strncpyz( servernodeptr->adrstr, adrstr, MAX_ADDRESSLENGTH );

    Q_strncpyz( servernodeptr->hostname, Info_ValueForKey( info, "hostname"), MAX_HOSTNAMELENGTH );
    Q_CleanStr( servernodeptr->hostname );
    Q_strupr( servernodeptr->hostname );

    Q_strncpyz( servernodeptr->mapname, Info_ValueForKey( info, "mapname"), MAX_MAPNAMELENGTH );
    Q_CleanStr( servernodeptr->mapname );
    Q_strupr( servernodeptr->mapname );

    servernodeptr->numclients = atoi( Info_ValueForKey( info, "clients") );
    servernodeptr->maxclients = atoi( Info_ValueForKey( info, "sv_maxclients") );
    servernodeptr->pingtime   = pingtime;
    servernodeptr->minPing    = atoi( Info_ValueForKey( info, "minPing") );
    servernodeptr->maxPing    = atoi( Info_ValueForKey( info, "maxPing") );
    servernodeptr->bPB = atoi( Info_ValueForKey( info, "punkbuster") );

    /*
    s = Info_ValueForKey( info, "nettype" );
    for (i=0; ;i++)
    {
    	if (!netnames[i])
    	{
    		servernodeptr->nettype = 0;
    		break;
    	}
    	else if (!Q_stricmp( netnames[i], s ))
    	{
    		servernodeptr->nettype = i;
    		break;
    	}
    }
    */
    servernodeptr->nettype = atoi(Info_ValueForKey(info, "nettype"));

    s = Info_ValueForKey( info, "game");
    i = atoi( Info_ValueForKey( info, "gametype") );
    if( i < 0 )
    {
        i = 0;
    }
    else if( i > 11 )
    {
        i = 12;
    }
    if( *s )
    {
        servernodeptr->gametype = i;//-1;
        Q_strncpyz( servernodeptr->gamename, s, sizeof(servernodeptr->gamename) );
    }
    else
    {
        servernodeptr->gametype = i;
        Q_strncpyz( servernodeptr->gamename, gamenames[i], sizeof(servernodeptr->gamename) );
    }
}


/*
=================
ArenaServers_InsertFavorites
 
Insert nonresponsive address book entries into display lists.
=================
*/
void ArenaServers_InsertFavorites( void )
{
    int		i;
    int		j;
    char	info[MAX_INFO_STRING];

    // resync existing results with new or deleted cvars
    info[0] = '\0';
    Info_SetValueForKey( info, "hostname", "No Response" );
    for (i=0; i<g_arenaservers.numfavoriteaddresses; i++)
    {
        // find favorite address in refresh list
        for (j=0; j<g_numfavoriteservers; j++)
            if (!Q_stricmp(g_arenaservers.favoriteaddresses[i],g_favoriteserverlist[j].adrstr))
                break;

        if ( j >= g_numfavoriteservers)
        {
            // not in list, add it
            ArenaServers_Insert( g_arenaservers.favoriteaddresses[i], info, ArenaServers_MaxPing() );
        }
    }
}


/*
=================
ArenaServers_LoadFavorites
 
Load cvar address book entries into local lists.
=================
*/
void ArenaServers_LoadFavorites( void )
{
    int				i;
    int				j;
    int				numtempitems;
    char			emptyinfo[MAX_INFO_STRING];
    char			adrstr[MAX_ADDRESSLENGTH];
    servernode_t	templist[MAX_FAVORITESERVERS];
    qboolean		found;

    found        = qfalse;
    emptyinfo[0] = '\0';

    // copy the old
    memcpy( templist, g_favoriteserverlist, sizeof(servernode_t)*MAX_FAVORITESERVERS );
    numtempitems = g_numfavoriteservers;

    // clear the current for sync
    memset( g_favoriteserverlist, 0, sizeof(servernode_t)*MAX_FAVORITESERVERS );
    g_numfavoriteservers = 0;

    // resync existing results with new or deleted cvars
    for (i=0; i<MAX_FAVORITESERVERS; i++)
    {
        Cvar_VariableStringBuffer( va("server%d",i+1), adrstr, MAX_ADDRESSLENGTH );
        if (!adrstr[0])
            continue;

        // quick sanity check to avoid slow domain name resolving
        // first character must be numeric
        if (adrstr[0] < '0' || adrstr[0] > '9')
            continue;

        // favorite server addresses must be maintained outside refresh list
        // this mimics local and global netadr's stored in client
        // these can be fetched to fill ping list
        strcpy( g_arenaservers.favoriteaddresses[g_numfavoriteservers], adrstr );

        // find this server in the old list
        for (j=0; j<numtempitems; j++)
            if (!Q_stricmp( templist[j].adrstr, adrstr ))
                break;

        if (j < numtempitems)
        {
            // found server - add exisiting results
            memcpy( &g_favoriteserverlist[g_numfavoriteservers], &templist[j], sizeof(servernode_t) );
            found = qtrue;
        }
        else
        {
            // add new server
            Q_strncpyz( g_favoriteserverlist[g_numfavoriteservers].adrstr, adrstr, MAX_ADDRESSLENGTH );
            g_favoriteserverlist[g_numfavoriteservers].pingtime = ArenaServers_MaxPing();
        }

        g_numfavoriteservers++;
    }

    g_arenaservers.numfavoriteaddresses = g_numfavoriteservers;

    if (!found)
    {
        // no results were found, reset server list
        // list will be automatically refreshed when selected
        g_numfavoriteservers = 0;
    }
}


/*
=================
ArenaServers_StopRefresh
=================
*/
static void ArenaServers_StopRefresh( void )
{
    if (!g_arenaservers.refreshservers)
        // not currently refreshing
        return;

    g_arenaservers.refreshservers = qfalse;

    if (g_servertype == AS_FAVORITES)
    {
        // nonresponsive favorites must be shown
        ArenaServers_InsertFavorites();
    }

    // final tally
    if (g_arenaservers.numqueriedservers >= 0)
    {
        g_arenaservers.currentping       = *g_arenaservers.numservers;
        g_arenaservers.numqueriedservers = *g_arenaservers.numservers;
    }

    // sort
    qsort( g_arenaservers.serverlist, *g_arenaservers.numservers, sizeof( servernode_t ), ArenaServers_Compare);

    ArenaServers_UpdateMenu();
}


/*
=================
ArenaServers_DoRefresh
=================
*/
static void ArenaServers_DoRefresh( void )
{
    int		i;
    int		j;
    int		time;
    int		maxPing;
    char	adrstr[MAX_ADDRESSLENGTH];
    char	info[MAX_INFO_STRING];

    if (uis.realtime < g_arenaservers.refreshtime)
    {
        if (g_servertype != AS_FAVORITES)
        {
            if (g_servertype == AS_LOCAL)
            {
                if (!LAN_GetServerCount(g_servertype))
                {
                    return;
                }
            }
            if (LAN_GetServerCount(g_servertype) < 0)
            {
                // still waiting for response
                return;
            }
        }
    }

    if (uis.realtime < g_arenaservers.nextpingtime)
    {
        // wait for time trigger
        return;
    }

    // trigger at 10Hz intervals
    g_arenaservers.nextpingtime = uis.realtime + 10;

    // process ping results
    maxPing = ArenaServers_MaxPing();
    for (i=0; i<MAX_PINGREQUESTS; i++)
    {
        LAN_GetPing( i, adrstr, MAX_ADDRESSLENGTH, &time );
        if (!adrstr[0])
        {
            // ignore empty or pending pings
            continue;
        }

        // find ping result in our local list
        for (j=0; j<MAX_PINGREQUESTS; j++)
            if (!Q_stricmp( adrstr, g_arenaservers.pinglist[j].adrstr ))
                break;

        if (j < MAX_PINGREQUESTS)
        {
            // found it
            if (!time)
            {
                time = uis.realtime - g_arenaservers.pinglist[j].start;
                if (time < maxPing)
                {
                    // still waiting
                    continue;
                }
            }

            if (time > maxPing)
            {
                // stale it out
                info[0] = '\0';
                time    = maxPing;
            }
            else
            {
                LAN_GetPingInfo( i, info, MAX_INFO_STRING );
            }

            // insert ping results
            ArenaServers_Insert( adrstr, info, time );

            // clear this query from internal list
            g_arenaservers.pinglist[j].adrstr[0] = '\0';
        }

        // clear this query from external list
        LAN_ClearPing( i );
    }

    // get results of servers query
    // counts can increase as servers respond
    if (g_servertype == AS_FAVORITES)
    {
        g_arenaservers.numqueriedservers = g_arenaservers.numfavoriteaddresses;
    }
    else
    {
        g_arenaservers.numqueriedservers = LAN_GetServerCount(g_servertype);
    }

//	if (g_arenaservers.numqueriedservers > g_arenaservers.maxservers)
//		g_arenaservers.numqueriedservers = g_arenaservers.maxservers;

    // send ping requests in reasonable bursts
    // iterate ping through all found servers
    for (i=0; i<MAX_PINGREQUESTS && g_arenaservers.currentping < g_arenaservers.numqueriedservers; i++)
    {
        if (LAN_GetPingQueueCount() >= MAX_PINGREQUESTS)
        {
            // ping queue is full
            break;
        }

        // find empty slot
        for (j=0; j<MAX_PINGREQUESTS; j++)
            if (!g_arenaservers.pinglist[j].adrstr[0])
                break;

        if (j >= MAX_PINGREQUESTS)
            // no empty slots available yet - wait for timeout
            break;

        // get an address to ping

        if (g_servertype == AS_FAVORITES)
        {
            strcpy( adrstr, g_arenaservers.favoriteaddresses[g_arenaservers.currentping] );
        }
        else
        {
            LAN_GetServerAddressString(g_servertype, g_arenaservers.currentping, adrstr, MAX_ADDRESSLENGTH );
        }

        strcpy( g_arenaservers.pinglist[j].adrstr, adrstr );
        g_arenaservers.pinglist[j].start = uis.realtime;

        Cbuf_ExecuteText( EXEC_NOW, va( "ping %s\n", adrstr )  );

        // advance to next server
        g_arenaservers.currentping++;
    }

    if (!LAN_GetPingQueueCount())
    {
        // all pings completed
        ArenaServers_StopRefresh();
        return;
    }

    // update the user interface with ping status
    ArenaServers_UpdateMenu();
}


/*
=================
ArenaServers_StartRefresh
=================
*/
static void ArenaServers_StartRefresh( void )
{
    int		i;
    char	myargs[32], protocol[32];

    memset( g_arenaservers.serverlist, 0, g_arenaservers.maxservers*sizeof(table_t) );

    for (i=0; i<MAX_PINGREQUESTS; i++)
    {
        g_arenaservers.pinglist[i].adrstr[0] = '\0';
        LAN_ClearPing( i );
    }

    g_arenaservers.refreshservers    = qtrue;
    g_arenaservers.currentping       = 0;
    g_arenaservers.nextpingtime      = 0;
    *g_arenaservers.numservers       = 0;
    g_arenaservers.numqueriedservers = 0;

    // allow max 5 seconds for responses
    g_arenaservers.refreshtime = uis.realtime + 5000;

    // place menu in zeroed state
    ArenaServers_UpdateMenu();

    if( g_servertype == AS_LOCAL )
    {
        Cbuf_ExecuteText( EXEC_APPEND, "localservers\n" );
        return;
    }

    if( g_servertype == AS_GLOBAL || g_servertype == AS_MPLAYER )
    {
        if( g_servertype == AS_GLOBAL )
        {
            i = 0;
        }
        else
        {
            i = 1;
        }

        switch( g_arenaservers.gametype.curvalue )
        {
        default:
        case GAMES_ALL:
            myargs[0] = 0;
            break;

        case GAMES_FFA:
            strcpy( myargs, " ffa" );
            break;

        case GAMES_TEAMPLAY:
            strcpy( myargs, " team" );
            break;

        case GAMES_TOURNEY:
            strcpy( myargs, " tourney" );
            break;

        case GAMES_CTF:
            strcpy( myargs, " ctf" );
            break;
        }


        if (g_emptyservers)
        {
            strcat(myargs, " empty");
        }

        if (g_fullservers)
        {
            strcat(myargs, " full");
        }

        protocol[0] = '\0';
        Cvar_VariableStringBuffer( "debug_protocol", protocol, sizeof(protocol) );
        if (strlen(protocol))
        {
            Cbuf_ExecuteText( EXEC_APPEND, va( "globalservers %d %s%s\n", i, protocol, myargs ));
        }
        else
        {
            Cbuf_ExecuteText( EXEC_APPEND, va( "globalservers %d %d%s\n", i, (int)Cvar_VariableValue( "protocol" ), myargs ) );
        }
    }
}


/*
=================
ArenaServers_SaveChanges
=================
*/
void ArenaServers_SaveChanges( void )
{
    int	i;

    for (i=0; i<g_arenaservers.numfavoriteaddresses; i++)
        Cvar_Set( va("server%d",i+1), g_arenaservers.favoriteaddresses[i] );

    for (; i<MAX_FAVORITESERVERS; i++)
        Cvar_Set( va("server%d",i+1), "" );
}


/*
=================
ArenaServers_Sort
=================
*/
void ArenaServers_Sort( int type )
{
    if( g_sortkey == type )
    {
        return;
    }

    g_sortkey = type;
    qsort( g_arenaservers.serverlist, *g_arenaservers.numservers, sizeof( servernode_t ), ArenaServers_Compare);
}


/*
=================
ArenaServers_SetType
=================
*/
void ArenaServers_SetType( int type )
{
    if (g_servertype == type)
        return;

    g_servertype = type;

    switch( type )
    {
    default:
    case AS_LOCAL:
        g_arenaservers.remove.generic.flags |= (QMF_INACTIVE|QMF_HIDDEN);
        g_arenaservers.serverlist = g_localserverlist;
        g_arenaservers.numservers = &g_numlocalservers;
        g_arenaservers.maxservers = MAX_LOCALSERVERS;
        break;

    case AS_GLOBAL:
        g_arenaservers.remove.generic.flags |= (QMF_INACTIVE|QMF_HIDDEN);
        g_arenaservers.serverlist = g_globalserverlist;
        g_arenaservers.numservers = &g_numglobalservers;
        g_arenaservers.maxservers = MAX_GLOBALSERVERS;
        break;

    case AS_FAVORITES:
        g_arenaservers.remove.generic.flags &= ~(QMF_INACTIVE|QMF_HIDDEN);
        g_arenaservers.serverlist = g_favoriteserverlist;
        g_arenaservers.numservers = &g_numfavoriteservers;
        g_arenaservers.maxservers = MAX_FAVORITESERVERS;
        break;

    case AS_MPLAYER:
        g_arenaservers.remove.generic.flags |= (QMF_INACTIVE|QMF_HIDDEN);
        g_arenaservers.serverlist = g_mplayerserverlist;
        g_arenaservers.numservers = &g_nummplayerservers;
        g_arenaservers.maxservers = MAX_GLOBALSERVERS;
        break;

    }

    if( !*g_arenaservers.numservers )
    {
        ArenaServers_StartRefresh();
    }
    else
    {
        // avoid slow operation, use existing results
        g_arenaservers.currentping       = *g_arenaservers.numservers;
        g_arenaservers.numqueriedservers = *g_arenaservers.numservers;
        ArenaServers_UpdateMenu();
    }
    strcpy(g_arenaservers.status.string,"hit refresh to update");
}

/*
=================
PunkBuster_Confirm
=================
*/
static void Punkbuster_ConfirmEnable( qboolean result )
{
    g_arenaservers.punkbuster.curvalue = Com_Clamp( 0, 1, Cvar_VariableValue( "cl_punkbuster" ) );
}

static void Punkbuster_ConfirmDisable( qboolean result )
{
    if (result)
    {
        UI_Message( punkbuster_msg );
    }
    g_arenaservers.punkbuster.curvalue = Com_Clamp( 0, 1, Cvar_VariableValue( "cl_punkbuster" ) );
}

/*
=================
ArenaServers_Event
=================
*/
static void ArenaServers_Event( void* ptr, int event )
{
    int		id;
    int value;

    id = ((menucommon_s*)ptr)->id;

    if( event != QM_ACTIVATED && id != ID_LIST )
    {
        return;
    }

    switch( id )
    {
    case ID_MASTER:
        value = g_arenaservers.master.curvalue;
        if (value >= 1)
        {
            value++;
        }
        Cvar_SetValue( "ui_browserMaster", value );
        ArenaServers_SetType( value );
        break;

    case ID_GAMETYPE:
        Cvar_SetValue( "ui_browserGameType", g_arenaservers.gametype.curvalue );
        g_gametype = g_arenaservers.gametype.curvalue;
        ArenaServers_UpdateMenu();
        break;

    case ID_SORTKEY:
        Cvar_SetValue( "ui_browserSortKey", g_arenaservers.sortkey.curvalue );
        ArenaServers_Sort( g_arenaservers.sortkey.curvalue );
        ArenaServers_UpdateMenu();
        break;

    case ID_SHOW_FULL:
        Cvar_SetValue( "ui_browserShowFull", g_arenaservers.showfull.curvalue );
        g_fullservers = g_arenaservers.showfull.curvalue;
        ArenaServers_UpdateMenu();
        break;

    case ID_SHOW_EMPTY:
        Cvar_SetValue( "ui_browserShowEmpty", g_arenaservers.showempty.curvalue );
        g_emptyservers = g_arenaservers.showempty.curvalue;
        ArenaServers_UpdateMenu();
        break;

    case ID_LIST:
        if( event == QM_GOTFOCUS )
        {
            ArenaServers_UpdatePicture();
        }
        break;

    case ID_SCROLL_UP:
        ScrollList_Key( &g_arenaservers.list, K_UPARROW );
        break;

    case ID_SCROLL_DOWN:
        ScrollList_Key( &g_arenaservers.list, K_DOWNARROW );
        break;

    case ID_BACK:
        ArenaServers_StopRefresh();
        ArenaServers_SaveChanges();
        UI_PopMenu();
        break;

    case ID_REFRESH:
        ArenaServers_StartRefresh();
        break;

    case ID_SPECIFY:
        UI_SpecifyServerMenu();
        break;

    case ID_CREATE:
        UI_StartServerMenu( qtrue );
        break;

    case ID_CONNECT:
        ArenaServers_Go();
        break;

    case ID_REMOVE:
        ArenaServers_Remove();
        ArenaServers_UpdateMenu();
        break;

    case ID_PUNKBUSTER:
        if (g_arenaservers.punkbuster.curvalue)
        {
            UI_ConfirmMenu_Style( "Enable Punkbuster?",  UI_CENTER|UI_INVERSE|UI_SMALLFONT, (voidfunc_f)NULL, Punkbuster_ConfirmEnable );
        }
        else
        {
            UI_ConfirmMenu_Style( "Disable Punkbuster?", UI_CENTER|UI_INVERSE|UI_SMALLFONT, (voidfunc_f)NULL, Punkbuster_ConfirmDisable );
        }
        break;
    }
}


/*
=================
ArenaServers_MenuDraw
=================
*/
static void ArenaServers_MenuDraw( void )
{
    if (g_arenaservers.refreshservers)
        ArenaServers_DoRefresh();

    Menu_Draw( &g_arenaservers.menu );
}


/*
=================
ArenaServers_MenuKey
=================
*/
static sfxHandle_t ArenaServers_MenuKey( int key )
{
    if( key == K_SPACE  && g_arenaservers.refreshservers )
    {
        ArenaServers_StopRefresh();
        return menu_move_sound;
    }

    if( ( key == K_DEL || key == K_KP_DEL ) && ( g_servertype == AS_FAVORITES ) &&
            ( Menu_ItemAtCursor( &g_arenaservers.menu) == &g_arenaservers.list ) )
    {
        ArenaServers_Remove();
        ArenaServers_UpdateMenu();
        return menu_move_sound;
    }

    if( key == K_MOUSE2 || key == K_ESCAPE )
    {
        ArenaServers_StopRefresh();
        ArenaServers_SaveChanges();
    }


    return Menu_DefaultKey( &g_arenaservers.menu, key );
}


/*
=================
ArenaServers_MenuInit
=================
*/
static void ArenaServers_MenuInit( void )
{
    int			i;
    int			type;
    int			y;
    int			value;
    static char	statusbuffer[MAX_STATUSLENGTH];

    // zero set all our globals
    memset( &g_arenaservers, 0 ,sizeof(arenaservers_t) );

    ArenaServers_Cache();

    g_arenaservers.menu.fullscreen = qtrue;
    g_arenaservers.menu.wrapAround = qtrue;
    g_arenaservers.menu.draw       = ArenaServers_MenuDraw;
    g_arenaservers.menu.key        = ArenaServers_MenuKey;

    g_arenaservers.banner.generic.type  = MTYPE_BTEXT;
    g_arenaservers.banner.generic.flags = QMF_CENTER_JUSTIFY;
    g_arenaservers.banner.generic.x	    = 320;
    g_arenaservers.banner.generic.y	    = 16;
    g_arenaservers.banner.string  		= "ARENA SERVERS";
    g_arenaservers.banner.style  	    = UI_CENTER;
    g_arenaservers.banner.color  	    = color_white;

    y = 80;
    g_arenaservers.master.generic.type			= MTYPE_SPINCONTROL;
    g_arenaservers.master.generic.name			= "Servers:";
    g_arenaservers.master.generic.flags			= QMF_PULSEIFFOCUS|QMF_SMALLFONT;
    g_arenaservers.master.generic.callback		= ArenaServers_Event;
    g_arenaservers.master.generic.id			= ID_MASTER;
    g_arenaservers.master.generic.x				= 320;
    g_arenaservers.master.generic.y				= y;
    g_arenaservers.master.itemnames				= master_items;

    y += SMALLCHAR_HEIGHT;
    g_arenaservers.gametype.generic.type		= MTYPE_SPINCONTROL;
    g_arenaservers.gametype.generic.name		= "Game Type:";
    g_arenaservers.gametype.generic.flags		= QMF_PULSEIFFOCUS|QMF_SMALLFONT;
    g_arenaservers.gametype.generic.callback	= ArenaServers_Event;
    g_arenaservers.gametype.generic.id			= ID_GAMETYPE;
    g_arenaservers.gametype.generic.x			= 320;
    g_arenaservers.gametype.generic.y			= y;
    g_arenaservers.gametype.itemnames			= servertype_items;

    y += SMALLCHAR_HEIGHT;
    g_arenaservers.sortkey.generic.type			= MTYPE_SPINCONTROL;
    g_arenaservers.sortkey.generic.name			= "Sort By:";
    g_arenaservers.sortkey.generic.flags		= QMF_PULSEIFFOCUS|QMF_SMALLFONT;
    g_arenaservers.sortkey.generic.callback		= ArenaServers_Event;
    g_arenaservers.sortkey.generic.id			= ID_SORTKEY;
    g_arenaservers.sortkey.generic.x			= 320;
    g_arenaservers.sortkey.generic.y			= y;
    g_arenaservers.sortkey.itemnames			= sortkey_items;

    y += SMALLCHAR_HEIGHT;
    g_arenaservers.showfull.generic.type		= MTYPE_RADIOBUTTON;
    g_arenaservers.showfull.generic.name		= "Show Full:";
    g_arenaservers.showfull.generic.flags		= QMF_PULSEIFFOCUS|QMF_SMALLFONT;
    g_arenaservers.showfull.generic.callback	= ArenaServers_Event;
    g_arenaservers.showfull.generic.id			= ID_SHOW_FULL;
    g_arenaservers.showfull.generic.x			= 320;
    g_arenaservers.showfull.generic.y			= y;

    y += SMALLCHAR_HEIGHT;
    g_arenaservers.showempty.generic.type		= MTYPE_RADIOBUTTON;
    g_arenaservers.showempty.generic.name		= "Show Empty:";
    g_arenaservers.showempty.generic.flags		= QMF_PULSEIFFOCUS|QMF_SMALLFONT;
    g_arenaservers.showempty.generic.callback	= ArenaServers_Event;
    g_arenaservers.showempty.generic.id			= ID_SHOW_EMPTY;
    g_arenaservers.showempty.generic.x			= 320;
    g_arenaservers.showempty.generic.y			= y;

    y += 3 * SMALLCHAR_HEIGHT;
    g_arenaservers.list.generic.type			= MTYPE_SCROLLLIST;
    g_arenaservers.list.generic.flags			= QMF_HIGHLIGHT_IF_FOCUS;
    g_arenaservers.list.generic.id				= ID_LIST;
    g_arenaservers.list.generic.callback		= ArenaServers_Event;
    g_arenaservers.list.generic.x				= 72;
    g_arenaservers.list.generic.y				= y;
    g_arenaservers.list.width					= MAX_LISTBOXWIDTH;
    g_arenaservers.list.height					= 11;
    g_arenaservers.list.itemnames				= (const char **)g_arenaservers.items;
    for( i = 0; i < MAX_LISTBOXITEMS; i++ )
    {
        g_arenaservers.items[i] = g_arenaservers.table[i].buff;
    }

    g_arenaservers.mappic.generic.type			= MTYPE_BITMAP;
    g_arenaservers.mappic.generic.flags			= QMF_LEFT_JUSTIFY|QMF_INACTIVE;
    g_arenaservers.mappic.generic.x				= 72;
    g_arenaservers.mappic.generic.y				= 80;
    g_arenaservers.mappic.width					= 128;
    g_arenaservers.mappic.height				= 96;
    g_arenaservers.mappic.errorpic				= ART_UNKNOWNMAP;

    g_arenaservers.arrows.generic.type			= MTYPE_BITMAP;
    g_arenaservers.arrows.generic.name			= ART_ARROWS0;
    g_arenaservers.arrows.generic.flags			= QMF_LEFT_JUSTIFY|QMF_INACTIVE;
    g_arenaservers.arrows.generic.callback		= ArenaServers_Event;
    g_arenaservers.arrows.generic.x				= 512+48;
    g_arenaservers.arrows.generic.y				= 240-64+16;
    g_arenaservers.arrows.width					= 64;
    g_arenaservers.arrows.height				= 128;

    g_arenaservers.up.generic.type				= MTYPE_BITMAP;
    g_arenaservers.up.generic.flags				= QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS|QMF_MOUSEONLY;
    g_arenaservers.up.generic.callback			= ArenaServers_Event;
    g_arenaservers.up.generic.id				= ID_SCROLL_UP;
    g_arenaservers.up.generic.x					= 512+48;
    g_arenaservers.up.generic.y					= 240-64+16;
    g_arenaservers.up.width						= 64;
    g_arenaservers.up.height					= 64;
    g_arenaservers.up.focuspic					= ART_ARROWS_UP;

    g_arenaservers.down.generic.type			= MTYPE_BITMAP;
    g_arenaservers.down.generic.flags			= QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS|QMF_MOUSEONLY;
    g_arenaservers.down.generic.callback		= ArenaServers_Event;
    g_arenaservers.down.generic.id				= ID_SCROLL_DOWN;
    g_arenaservers.down.generic.x				= 512+48;
    g_arenaservers.down.generic.y				= 240+16;
    g_arenaservers.down.width					= 64;
    g_arenaservers.down.height					= 64;
    g_arenaservers.down.focuspic				= ART_ARROWS_DOWN;

    y = 376;
    g_arenaservers.status.generic.type		= MTYPE_TEXT;
    g_arenaservers.status.generic.x			= 320;
    g_arenaservers.status.generic.y			= y;
    g_arenaservers.status.string			= statusbuffer;
    g_arenaservers.status.style				= UI_CENTER|UI_SMALLFONT;
    g_arenaservers.status.color				= menu_text_color;

    y += SMALLCHAR_HEIGHT;
    g_arenaservers.statusbar.generic.type   = MTYPE_TEXT;
    g_arenaservers.statusbar.generic.x	    = 320;
    g_arenaservers.statusbar.generic.y	    = y;
    g_arenaservers.statusbar.string	        = "";
    g_arenaservers.statusbar.style	        = UI_CENTER|UI_SMALLFONT;
    g_arenaservers.statusbar.color	        = text_color_normal;

    g_arenaservers.remove.generic.type		= MTYPE_BITMAP;
    g_arenaservers.remove.generic.name		= ART_REMOVE0;
    g_arenaservers.remove.generic.flags		= QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS;
    g_arenaservers.remove.generic.callback	= ArenaServers_Event;
    g_arenaservers.remove.generic.id		= ID_REMOVE;
    g_arenaservers.remove.generic.x			= 450;
    g_arenaservers.remove.generic.y			= 86;
    g_arenaservers.remove.width				= 96;
    g_arenaservers.remove.height			= 48;
    g_arenaservers.remove.focuspic			= ART_REMOVE1;

    g_arenaservers.back.generic.type		= MTYPE_BITMAP;
    g_arenaservers.back.generic.name		= ART_BACK0;
    g_arenaservers.back.generic.flags		= QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS;
    g_arenaservers.back.generic.callback	= ArenaServers_Event;
    g_arenaservers.back.generic.id			= ID_BACK;
    g_arenaservers.back.generic.x			= 0;
    g_arenaservers.back.generic.y			= 480-64;
    g_arenaservers.back.width				= 128;
    g_arenaservers.back.height				= 64;
    g_arenaservers.back.focuspic			= ART_BACK1;

    g_arenaservers.specify.generic.type	    = MTYPE_BITMAP;
    g_arenaservers.specify.generic.name		= ART_SPECIFY0;
    g_arenaservers.specify.generic.flags    = QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS;
    g_arenaservers.specify.generic.callback = ArenaServers_Event;
    g_arenaservers.specify.generic.id	    = ID_SPECIFY;
    g_arenaservers.specify.generic.x		= 128;
    g_arenaservers.specify.generic.y		= 480-64;
    g_arenaservers.specify.width  		    = 128;
    g_arenaservers.specify.height  		    = 64;
    g_arenaservers.specify.focuspic         = ART_SPECIFY1;

    g_arenaservers.refresh.generic.type		= MTYPE_BITMAP;
    g_arenaservers.refresh.generic.name		= ART_REFRESH0;
    g_arenaservers.refresh.generic.flags	= QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS;
    g_arenaservers.refresh.generic.callback	= ArenaServers_Event;
    g_arenaservers.refresh.generic.id		= ID_REFRESH;
    g_arenaservers.refresh.generic.x		= 256;
    g_arenaservers.refresh.generic.y		= 480-64;
    g_arenaservers.refresh.width			= 128;
    g_arenaservers.refresh.height			= 64;
    g_arenaservers.refresh.focuspic			= ART_REFRESH1;

    g_arenaservers.create.generic.type		= MTYPE_BITMAP;
    g_arenaservers.create.generic.name		= ART_CREATE0;
    g_arenaservers.create.generic.flags		= QMF_LEFT_JUSTIFY|QMF_PULSEIFFOCUS;
    g_arenaservers.create.generic.callback	= ArenaServers_Event;
    g_arenaservers.create.generic.id		= ID_CREATE;
    g_arenaservers.create.generic.x			= 384;
    g_arenaservers.create.generic.y			= 480-64;
    g_arenaservers.create.width				= 128;
    g_arenaservers.create.height			= 64;
    g_arenaservers.create.focuspic			= ART_CREATE1;

    g_arenaservers.go.generic.type			= MTYPE_BITMAP;
    g_arenaservers.go.generic.name			= ART_CONNECT0;
    g_arenaservers.go.generic.flags			= QMF_RIGHT_JUSTIFY|QMF_PULSEIFFOCUS;
    g_arenaservers.go.generic.callback		= ArenaServers_Event;
    g_arenaservers.go.generic.id			= ID_CONNECT;
    g_arenaservers.go.generic.x				= 640;
    g_arenaservers.go.generic.y				= 480-64;
    g_arenaservers.go.width					= 128;
    g_arenaservers.go.height				= 64;
    g_arenaservers.go.focuspic				= ART_CONNECT1;

    g_arenaservers.punkbuster.generic.type			= MTYPE_SPINCONTROL;
    g_arenaservers.punkbuster.generic.name			= "Punkbuster:";
    g_arenaservers.punkbuster.generic.flags			= QMF_PULSEIFFOCUS|QMF_SMALLFONT;
    g_arenaservers.punkbuster.generic.callback		= ArenaServers_Event;
    g_arenaservers.punkbuster.generic.id			= ID_PUNKBUSTER;
    g_arenaservers.punkbuster.generic.x				= 480+32;
    g_arenaservers.punkbuster.generic.y				= 144;
    g_arenaservers.punkbuster.itemnames				= punkbuster_items;

    g_arenaservers.pblogo.generic.type			= MTYPE_BITMAP;
    g_arenaservers.pblogo.generic.name			= ART_PUNKBUSTER;
    g_arenaservers.pblogo.generic.flags			= QMF_LEFT_JUSTIFY|QMF_INACTIVE;
    g_arenaservers.pblogo.generic.x				= 526;
    g_arenaservers.pblogo.generic.y				= 176;
    g_arenaservers.pblogo.width					= 32;
    g_arenaservers.pblogo.height				= 16;
    g_arenaservers.pblogo.errorpic				= ART_UNKNOWNMAP;

    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.banner );

    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.master );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.gametype );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.sortkey );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.showfull);
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.showempty );

    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.mappic );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.list );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.status );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.statusbar );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.arrows );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.up );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.down );

    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.remove );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.back );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.specify );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.refresh );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.create );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.go );

    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.punkbuster );
    Menu_AddItem( &g_arenaservers.menu, (void*) &g_arenaservers.pblogo );

    ArenaServers_LoadFavorites();

    g_servertype = Com_Clamp( 0, 3, ui_browserMaster->integer );
    // hack to get rid of MPlayer stuff
    value = g_servertype;
    if (value >= 1)
        value--;
    g_arenaservers.master.curvalue = value;

    g_gametype = Com_Clamp( 0, 4, ui_browserGameType->integer );
    g_arenaservers.gametype.curvalue = g_gametype;

    g_sortkey = Com_Clamp( 0, 4, ui_browserSortKey->integer );
    g_arenaservers.sortkey.curvalue = g_sortkey;

    g_fullservers = Com_Clamp( 0, 1, ui_browserShowFull->integer );
    g_arenaservers.showfull.curvalue = g_fullservers;

    g_emptyservers = Com_Clamp( 0, 1, ui_browserShowEmpty->integer );
    g_arenaservers.showempty.curvalue = g_emptyservers;

    g_arenaservers.punkbuster.curvalue = Com_Clamp( 0, 1, Cvar_VariableValue( "cl_punkbuster" ) );

    // force to initial state and refresh
    type = g_servertype;
    g_servertype = -1;
    ArenaServers_SetType( type );

    Cvar_Get("debug_protocol", "", 0 );
}


/*
=================
ArenaServers_Cache
=================
*/
void ArenaServers_Cache( void )
{
    RE_RegisterShaderNoMip( ART_BACK0 );
    RE_RegisterShaderNoMip( ART_BACK1 );
    RE_RegisterShaderNoMip( ART_CREATE0 );
    RE_RegisterShaderNoMip( ART_CREATE1 );
    RE_RegisterShaderNoMip( ART_SPECIFY0 );
    RE_RegisterShaderNoMip( ART_SPECIFY1 );
    RE_RegisterShaderNoMip( ART_REFRESH0 );
    RE_RegisterShaderNoMip( ART_REFRESH1 );
    RE_RegisterShaderNoMip( ART_CONNECT0 );
    RE_RegisterShaderNoMip( ART_CONNECT1 );
    RE_RegisterShaderNoMip( ART_ARROWS0  );
    RE_RegisterShaderNoMip( ART_ARROWS_UP );
    RE_RegisterShaderNoMip( ART_ARROWS_DOWN );
    RE_RegisterShaderNoMip( ART_UNKNOWNMAP );
    RE_RegisterShaderNoMip( ART_PUNKBUSTER );
}


/*
=================
UI_ArenaServersMenu
=================
*/
void UI_ArenaServersMenu( void )
{
    ArenaServers_MenuInit();
    UI_PushMenu( &g_arenaservers.menu );
}
